package dhadi.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class GameStrategy {

    public GameStrategy() {

    }
    
    /**
     * Returns true if player picks can move to the
     * given node and that forms dhadi, otherwise returns false.
     * 
     * Algorithm 'FINDING DHADI FORMATION WITH NODE'
     * 
     * 1. Find nodes adjacent to 'node'
     * 2. Find 'player' picks on these nodes call it 'adjacentPicks'
     * 3. If no picks found then return false
     * 4. Find adjacent triodes to 'node'.
     * 5. For each triode
     *    5.1 Find nodes on the triode
     *    5.2 Remove the 'node' from above nodes
     *    5.3 Find 'player' picks on nodes call it 'playerTriodePicks'
     *    5.4 If size of 'playerTriodePicks' is 2 then
     *        5.4.1 Subtract 'playerTriodePicks' from 'adjacentPicks' nodes - call it A
     *        5.4.2 Subtract 'adjacentPicks' from 'playerTriodePicks' nodes - call it B
     *        5.4.3 if A.size() >= 0 and B.size() > 0 return true
     *        5.4.3 else continue
     *    5.5 else continue with next triode
     * 6. return false
     */
    public static boolean canNodeFormDhadi(
            Node node, Player player, Board board) {
        
        List<Node> adjacentNodes = node.getAdjusantNodes();
        List<Pick> adjacentPicks = board.getPlayerPicksOnNodes(adjacentNodes, player);
        if (adjacentPicks.isEmpty()) return false;
        List<String> adjacentTrodes = board.findTouchingTriodes(node);
        for (String adjacentTriode : adjacentTrodes) {
            List<Node> triodeNodes = board.getTriodeNodes(adjacentTriode);
            triodeNodes.remove(node);
            List<Pick> playerTriodePicks = board.getPlayerPicksOnNodes(triodeNodes, player);
            if (playerTriodePicks.size() < 2) continue;

            List<?> a = GameStrategy.subtract(playerTriodePicks, adjacentPicks);
            List<?> b = GameStrategy.subtract(adjacentPicks, playerTriodePicks);
            if (a.size() >= 0 && b.size() > 0) {
                return true;
            }   
        }
        return false;
    }
    /*public static boolean canNodeFormDhadi(
            Node node, Player player, Board board) {
        
        List<Node> adjacentNodes = node.getAdjusantNodes();
        List<Pick> adjacentPicks = board.getPlayerPicksOnNodes(adjacentNodes, player);
        if (adjacentPicks.isEmpty()) return false;
        List<String> adjacentTrodes = board.findTouchingTriodes(node);
        for (String adjacentTriode : adjacentTrodes) {
            List<Node> triodeNodes = board.getTriodeNodes(adjacentTriode);
            triodeNodes.remove(node);
            List<Pick> playerTriodePicks = board.getPlayerPicksOnNodes(triodeNodes, player);
            if (playerTriodePicks.size() == 2) {
                List<Pick> tempAdjPicks = new ArrayList<Pick>();
                tempAdjPicks.addAll(adjacentPicks);
                for (Pick playerTriodePick : playerTriodePicks) {
                    tempAdjPicks.remove(playerTriodePick);
                }
                if (tempAdjPicks.isEmpty()) continue;
                else return true;
            } else continue;
        }
        return false;
    }*/

    /**
     * Returns list of Moves which can make dhadi
     * with the given movablePicks 
     * ( a subset of picks or all movable picks of 
     * the player ).
     * 
     * @param player
     * @param movablePicks
     * @param board
     * @return
     */
    public static List<Move> findDhadiMove(Player player, 
            List<Pick> movablePicks, Board board) {

        /**
         * Algorithm
         * 1. For each movable pick
         *    1.1 Find movable nodes
         *    1.2 Find adjacent triodes for the pick (SetA)
         *    1.3 For each movable nodes
         *        1.3.1 Find adjacent triodes for the node (SetB)
         *        1.3.2 Subtract node triodes from pick triodes ( SetB - SetA )
         *        1.3.1 From the resultant triode Find if node participates in 
         *              forming dhadi (count of own picks on this triode should be 2)
         * 
         */

        List<Move> dhadiMoves = new ArrayList<Move>(); 

        //1. For eacch movable pick
        for (Pick movablePick : movablePicks) {
            //1.1 Find movable nodes
            List<Node> movableNodes = 
                movablePick.getNode().getAdjusantEmptyNodes();
            //1.2 Find adjacent triodes for the pick (SetA)
            List<String> picksAdjacentTriodes = 
                board.findTouchingTriodes(movablePick.getNode());
            //1.3 For each movable nodes
            for (Node movableNode : movableNodes) {
                //1.3.1 Find adjacent triodes for the node (SetB)
                List<String> nodesAdjacentTriodes = 
                    board.findTouchingTriodes(movableNode);
                //1.3.2 Subtract node triodes from pick triodes ( SetB - SetA )
                for (String pickTriode : picksAdjacentTriodes) {
                    nodesAdjacentTriodes.remove(pickTriode);
                }
                if (nodesAdjacentTriodes.isEmpty()) continue;
                String resultantTriode = nodesAdjacentTriodes.get(0);
                if (board.getPlayerPicksOnTriode(
                        resultantTriode, player).size() == 2) {
                    dhadiMoves.add(Move.createOnBoardMove(
                            movablePick,player,movableNode));
                }
            }
        }

        return dhadiMoves;
    }

    /**
     * L shape formation is a double dhadi maker.
     * Identify such formation and prevent in 
     * advance. Other way make use of this node
     * and made a double dhadi.
     * 
     *  
     * @return
     */
    public static Node findLstrategyNode(Player player, Board board) {
        /*
         * Algorithm
         * 
         * 1. For each pick on board
         * 2. Find adjacent triodes
         * 3. For each adjacent triodes find tangent triodes
         *     3.1. Of which find opponent-only-occupied triodes
         *     3.2. Find node that blocks L formation 
         *          3.2.1 Find node-path between opponent picks
         *          3.2.2 any node in the path is a blocking node
         */
        List<Pick> onBoardPicks = player.getOpponent().getPicksOnBoard();
        if (onBoardPicks.isEmpty() || onBoardPicks.size() < 2) return null;
        for (Pick onBoardPick : onBoardPicks) {
            List<String> adjacentTriodes = board.findTouchingTriodes(onBoardPick.getNode());
            // TODO: If adjacent triodes got own picks remove such triode

            List<String> tangentTriodes = new ArrayList<String>();
            for (String adjacentTriode : adjacentTriodes) {
                tangentTriodes.addAll(board.findTouchingTriodes(adjacentTriode));
            }

            //3.1 Of which find opponent-only-occupied triodes
            List<String> opponentOnlyOccupiedTriodes = new ArrayList<String>();
            for (String tangentTriode : tangentTriodes) {
                if (board.isOnlyOpponentOccupied(tangentTriode, player)) {
                    opponentOnlyOccupiedTriodes.add(tangentTriode);
                }
            }

            if (opponentOnlyOccupiedTriodes.isEmpty()) continue;

            // Remove the triodes on which the current pick is  
            for (String adjacentTriode : adjacentTriodes) {
                opponentOnlyOccupiedTriodes.remove(adjacentTriode);
            }

            Node triodeIntersection;
            for (String adjacentTriode : adjacentTriodes) {

                // If any of cpu player picks are on the triode, then
                // opponent cannot make a double dhadi, even if 
                // his move cause L formation
                if (!board.isOnlyOpponentOccupied(adjacentTriode, player))
                    continue;

                for (String opponentOnlyOccupiedTriode : opponentOnlyOccupiedTriodes) {
                    triodeIntersection = board.findTriodeIntersection(adjacentTriode,opponentOnlyOccupiedTriode);
                    if (triodeIntersection != null && triodeIntersection.isNodeEmpty()) {
                        return triodeIntersection;
                    }
                }
            }
        }
        return null;
    }
    
    /**
     * Returns a node that prevents 'Corner Strategy' of opponent.
     * 
     * Corner Strategy that forms a sure dhadi.
     * 
     * O-------.-------.
     * |       |       | 
     * |  .----.----.  |
     * |  |    |    |  |
     * |  |  .-.-.  |  |
     * |  |  |   |  |  |
     * .--.--.   .--.--.
     * |  |  |   |  |  | 
     * |  |  .-.-.  |  |
     * |  |    |    |  | 
     * |  .----.----.  |
     * |       |       | 
     * .-------.-------O
     * 
     * Prevention, this method returns node at 'X' 
     * in below representation, if opponent pick is
     * at 'O'
     * 
     * O-------.-------.
     * |       |       | 
     * |  .----.----.  |
     * |  |    |    |  |
     * |  |  .-.-.  |  |
     * |  |  |   |  |  |
     * .--.--.   .--.--.
     * |  |  |   |  |  | 
     * |  |  .-.-.  |  |
     * |  |    |    |  | 
     * |  .----.----.  |
     * |       |       | 
     * .-------.-------X
     * 
     * @param player
     * @param board
     * @return
     */
    public static Node findCornerStrategyNode(Player player, Board board) {
        List<Pick> onBoardPicks = player.getOpponent().getPicksOnBoard();
        if (onBoardPicks.isEmpty()) return null;
        for (Pick onBoardPick : onBoardPicks) {
            if (!onBoardPick.getNode().isCornerNode()) continue;
            
            Node oppositeCornerNode = 
                board.findOppositeCornerNode(onBoardPick.getNode());
            if (!oppositeCornerNode.isNodeEmpty()) continue;
            
            List<String> touchingTriodes = 
                board.findTouchingTriodes(onBoardPick.getNode());
            for (String touchingTriode : touchingTriodes) {
                // If player has no picks on this triode
                // then pick opposite empty node on this triode
                // 
                if (board.getPlayerPicksOnTriode(
                        touchingTriode, player).size() == 0) {
                    return oppositeCornerNode;             
                }
            }
        }
        return null;
    }
    
    /**
     * Find moves, moving wich could create dhadi forming
     * move to opponent.
     * 
     * @param player
     * @param movablePicks
     * @param board
     * @return
     */ 
    public static List<Move> findMovesThatMakeDhadiForOpponent(Player player, 
            List<Pick> movablePicks, Board board) {
        /**
         * Algorithm
         * 
         * 1. For each pick from the given movablePicks of the given Player
         *    1.1 Find movable Nodes
         *    1.2 FIND IF OPPONENT CAN FORM DHADI WITH pick.node
         *    1.3 If above step returns false then continue else proceed with next pick 
         *    1.4 For each node from movable Node
         *        1.2.1 Create Move for the pick to the node
         *        1.2.2 add the move to 'possibleMoves'
         *    1.5 Return 'possibleMoves'
         *  
         */
        List<Move> possibleMoves = new ArrayList<Move>();
        if (movablePicks == null || movablePicks.isEmpty()) return possibleMoves;
        for (Pick pick : movablePicks ) {
            if (GameStrategy.canNodeFormDhadi(pick.getNode(),player.getOpponent(),board)) {
                List<Node> movableNodes = pick.getNode().getAdjusantEmptyNodes();
                for (Node movableNode : movableNodes) {
                    possibleMoves.add(Move.createOnBoardMove(
                            pick, player, movableNode));
                }
            }
        }
        return possibleMoves;
    }
    
    public static List<Move> findOpponentDhadiBlockers(CpuPlayer player,
            List<Pick> movablePicks, Board board) {
        /**
         * Algorithm
         * 
         * 1. Find movable picks
         * 2. For each movable pick
         *    2.1 Find movable nodes
         *    2.2 For each movable 'node'
         *        2.2.1 Find if 'node' can form dhad for opponent
         *        2.2.2 If true, then create a new move to 'node' 
         *              with 'pick' and add to 'possibleMoves'
         *        2.2.3 If false, continue with next node
         * 3. return 'possibleMoves'
         * 
         */
        List<Move> possibleMoves = new ArrayList<Move>();
        if (movablePicks == null || movablePicks.isEmpty()) return possibleMoves;
        for (Pick pick : movablePicks ) {
            List<Node> movableNodes = board.findMovableNodes(pick);
            for (Node movableNode : movableNodes) {
                if (GameStrategy.canNodeFormDhadi(movableNode,player.getOpponent(),board)) {
                    possibleMoves.add(Move.createOnBoardMove(
                            pick, player, movableNode));
                }
            }
        }
        return possibleMoves;        
    }
    
    /**
     * ABANDONED LOGIC
     * 
     * This logic require state.
     * 
     * The idea is to force a user to move his picks so that
     * he opens his/her dhadi pick or a dhadi blocking pick.
     * 
     * In other way, this logic tries to make the opponent
     * to make a loosing move as his only choice.
     */
    
    /**
     * ABANDONED LOGIC 
     * 
     * Find the opponent pick, which is targetted
     * to be moved. Until this pick is moved the 
     * logic should block opponent picks.
     * 
     * Strategy to find such picks.
     * 1. Opponent pick that is blocking a dhadi.
     */
    /**
     * ABANDONED LOGIC
     * 
     * 1. Blocking move. 
     *    * Find Moves such that an empty node created by this move cannot be 
     *      used by the opponent
     *    * Find Other Moves 
     * 
     */
    /**
     * Find moves which blocks most picks and opens least 
     * picks
     * 
     * 1. For each movable pick.
     *    1.1 Find opponent opening picks to the current pick's 
     *        node (pO) 
     *    1.1 Find nodes to move
     *    1.2 For each node
     *        1.2.1 Create Move
     *        1.2.2 Find number of opponent blocking picks (pB)
     *        1.2.3 add Move to possible moves with the
     *              weight ( pB - pO )
     *              
     * @param player
     * @param movablePicks
     * @param board
     * @return 
     */
    public static WeightedList<Move> findBestBlockingMove(Player player,
            List<Pick> movablePicks, Board board) {
        WeightedList<Move> weightedList = new WeightedList<Move>(); 
        for (Pick movablePick : movablePicks) {
            int pO = board.findMovablePicks(player.getOpponent(),
                    movablePick.getNode()).size();
            List<Node> movableNodes = board.findMovableNodes(movablePick);
            for (Node movableNode : movableNodes) {
                Move move = Move.createOnBoardMove(
                        movablePick, player, movableNode);
                int pB = board.findPicksOnlyMovableToNode(player.getOpponent(),
                        movableNode).size();
                weightedList.add(move, (pB-pO)*1.0);
            }
        }
        return weightedList;
    }

    /**
     * Returns moves for the given player, moving which can
     * cause opponents dhadi to open.
     * 
     * These moves the player does give least priority to use.
     * 
     * 
     * @param cpuPlayer
     * @param movablePicks
     * @param board
     * @return
     */
    public static List<Move> findMovesThatOpenDhadiForOpponent(
            Player player, List<Pick> movablePicks, Board board) {
        /**
         * Algorithm
         * 
         * 1. For each 'pick' from the given movablePicks of the given Player
         *    1.1 Find movable Nodes
         *    1.2 FIND IF OPPONENT CAN OPEN DHADI BY MOVING HIS/HER PICK TO 
         *        THE 'pick.node'
         *    1.3 If above step returns false then continue else proceed next steps. 
         *    1.4 For each node from movable Node
         *        1.2.1 Create Move for the pick to the node
         *        1.2.2 add the move to 'possibleMoves'
         *    1.5 Return 'possibleMoves'
         *  
         */
        List<Move> possibleMoves = new ArrayList<Move>();
        if (movablePicks == null || movablePicks.isEmpty()) return possibleMoves;
        for (Pick pick : movablePicks ) {
            if (GameStrategy.canDhadiPickMoveToNode(pick.getNode(),player.getOpponent())) {
                List<Node> movableNodes = pick.getNode().getAdjusantEmptyNodes();
                for (Node movableNode : movableNodes) {
                    possibleMoves.add(Move.createOnBoardMove(
                            pick, player, movableNode));
                }
            }
        }
        return possibleMoves;
    }

    /**
     * Returns true if any of the pick from the dhadi formation of 'player'
     * can move to 'node' otherwise false.
     * 
     * @param node
     * @param opponent
     * @param board
     * @return
     */
    public static boolean canDhadiPickMoveToNode(Node node, Player player) {
        List<Pick> dhadiPicks = player.findDhadiFormingPicks();
        if (dhadiPicks.isEmpty()) return false;
        for (Pick pick : dhadiPicks) {
            if (node.isAdjacent(pick.getNode())) return true;
        }
        return false;
    }
    
    /**
     * Returns a - b
     * 
     * References to a, b are unchanged, that is their contents
     * remain same after calling to this method.
     * 
     * @param a
     * @param b
     * @return
     */
    private static List<?> subtract(List<?> a,
            List<?> b) {
        List<Object> reminder = new ArrayList<Object>();
        for (Object o : a) {
            if (!b.contains(o)) 
                reminder.add(o);
        } 
        return reminder;
    }    
}
